﻿using DoNet.Common.Helpers;
using DoNet.Common.Pages;
using DoNet.Core;
using DoNet.Core.Dtos;
using DoNet.Core.Models;
using DoNet.Core.UnitOfWork;
using DoNet.System.Dtos;
using DoNet.System.IRepositories;
using DoNet.System.IServices;
using DoNet.System.Models;
using System.Security.Cryptography;
using System.Text;

namespace DoNet.System.Services
{
    /// <summary>
    /// 
    /// </summary>
    public class TokenService : BaseService<Token, TokenOutputDto>, ITokenService
    {
        private readonly IUnitOfWork unitOfWork;
        private readonly ITokenRepository repository;
        private readonly ITokenLogRepository tokenLogRepository;
        private readonly IAppRepository appRepository;
        public TokenService(IUnitOfWork _unitOfWork, IRepository<Token> _baseRepository, ITokenRepository _repository, ITokenLogRepository _tokenLogRepository, IAppRepository _appRepository)
        {
            unitOfWork = _unitOfWork;
            baseRepository = _baseRepository;
            repository = _repository;
            tokenLogRepository = _tokenLogRepository;
            appRepository = _appRepository;
        }

        #region 同步方法
        /// <summary>
        /// 同步获取当前AppId未过期的Token
        /// </summary>
        /// <param name="appId"></param>
        /// <returns></returns>
        public Token GetTokenByAppId(string appId)
        {
            return repository.GetTokenByAppId(appId);
        }
        /// <summary>
        /// 同步获取当前token或者刷新token
        /// </summary>
        /// <param name="token">当前token或者刷新token</param>
        /// <returns></returns>
        public Token GetTokenIsRevoked(string token)
        {
            return repository.GetTokenIsRevoked(token);
        }
        /// <summary>
        /// 同步根据刷新token获取当前token
        /// </summary>
        /// <param name="refreshToken">刷新token</param>
        /// <returns></returns>
        public Token GetTokenByRefreshToken(string refreshToken)
        {
            return repository.GetTokenByRefreshToken(refreshToken);
        }
        /// <summary>
        /// 同步获取token
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="accessExpire"></param>
        /// <param name="refreshExpire"></param>
        /// <param name="claims"></param>
        /// <param name="creatorUserId"></param>
        /// <param name="creatorTime"></param>
        /// <returns></returns>
        public AccessTokenOut? GetTokenPair(string appId, string appSecret, int accessExpire, int refreshExpire, Dictionary<string, string> claims, long creatorUserId, DateTime creatorTime)
        {
            var tokenModel = GetTokenByAppId(appId);
            if (tokenModel == null)
            {
                string accessToken = TokenHelper.GenerateToken(appId, appSecret, accessExpire, claims);
                string refreshToken = TokenHelper.GenerateToken(appId, appSecret, refreshExpire, new Dictionary<string, string>());
                Token token = new Token()
                {
                    Id = IdGeneratorHelper.IdSnowflake(),
                    AccessToken = accessToken,
                    AccessExpires = accessExpire,
                    AccessExpiresDate = DateTime.UtcNow.AddMinutes(accessExpire),
                    RefreshToken = refreshToken,
                    RefreshExpires = refreshExpire,
                    RefreshExpiresDate = DateTime.UtcNow.AddMinutes(refreshExpire),
                    CreatorTime = creatorTime,
                    CreatorUserId = creatorUserId,
                    LastModifyTime = creatorTime,
                    LastModifyUserId = creatorUserId
                };
                int num = Insert(token);
                if (num > 0)
                {
                    return new AccessTokenOut { access_token = accessToken, access_expire = accessExpire, refresh_token = refreshToken, refresh_expire = refreshExpire };
                }
            }
            else
            {
                return new AccessTokenOut { access_token = tokenModel.AccessToken, access_expire = tokenModel.AccessExpires, refresh_token = tokenModel.RefreshToken, refresh_expire = tokenModel.RefreshExpires };
            }
            return default;
        }
        /// <summary>
        /// 同步验证token
        /// </summary>
        /// <param name="token"></param>
        /// <returns>true 有效的(刷新)令牌；false 无效的(刷新)令牌</returns>
        public bool ValidateToken(string token)
        {
            Tuple<string, string, Dictionary<string, string>> result = TokenHelper.GetParseClaims(token, true);
            string payload = result.Item1;
            string oldSignature = result.Item2;
            Dictionary<string, string> claims = result.Item3;
            if (!string.IsNullOrEmpty(payload))
            {
                if (!string.IsNullOrEmpty(oldSignature))
                {
                    if (claims.Count > 0)
                    {
                        string appId = claims["appid"];
                        var expiryTicks = long.Parse(claims["exp"]);
                        if (DateTime.UtcNow.Ticks < expiryTicks)
                        {
                            //判断是否已经吊销
                            var model = GetTokenIsRevoked(token);
                            if (model != null)
                            {
                                var app = appRepository.GetApp(appId);
                                if (app != null)
                                {
                                    using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(app.AppSecret));
                                    string signature = Convert.ToBase64String(hmac.ComputeHash(Convert.FromBase64String(payload)));
                                    return oldSignature == signature;
                                }
                            }
                        }
                    }
                }
            }
            return false;
        }
        /// <summary>
        /// 同步通过RefreshToken获取旧Token
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <param name="accessExpire"></param>
        /// <returns></returns>
        public RefreshTokenOut? ForceRefresh(string refreshToken, int accessExpire, long lastModifyUserId, DateTime lastModifyTime)
        {
            var model = GetTokenByRefreshToken(refreshToken);
            if (model != null)
            {
                Dictionary<string, string> claims = TokenHelper.GetParseClaims(model.AccessToken, true).Item3;
                if (claims.Count > 0)
                {
                    string appId = claims["appid"];
                    var app = appRepository.GetApp(appId);
                    if (app != null)
                    {
                        Dictionary<string, string> claimsDic = TokenHelper.GetParseClaims(model.AccessToken, false).Item3;
                        string newToken = TokenHelper.GenerateToken(appId, app.AppSecret, accessExpire, claimsDic);
                        if (!string.IsNullOrEmpty(newToken))
                        {
                            unitOfWork.BeginTran();
                            try
                            {
                                repository.Update(newToken, accessExpire, lastModifyUserId, lastModifyTime, model.Id);//更新令牌;
                                tokenLogRepository.Insert(new TokenLog() { NewAccessToken = newToken, OldAccessToken = model.AccessToken, OldAccessExpiresDate = model.AccessExpiresDate, CreatedDate = DateTime.Now }); //记录刷新记录
                                unitOfWork.CommitTran();

                                return new RefreshTokenOut()
                                {
                                    access_token = newToken,
                                    access_expire = accessExpire
                                };
                            }
                            //catch (Exception ex)
                            catch
                            {
                                unitOfWork.RollbackTran();
                            }
                        }
                    }
                }
            }
            return default;
        }
        #endregion 同步方法

        #region 异步方法
        /// <summary>
        /// 异步获取当前AppId未过期的Token
        /// </summary>
        /// <param name="appId"></param>
        /// <returns></returns>
        public async Task<Token> GetTokenByAppIdAsync(string appId)
        {
            return await repository.GetTokenByAppIdAsync(appId);
        }
        /// <summary>
        /// 异步获取当前token或者刷新token
        /// </summary>
        /// <param name="token">当前token或者刷新token</param>
        /// <returns></returns>
        public async Task<Token> GetTokenIsRevokedAsync(string token)
        {
            return await repository.GetTokenIsRevokedAsync(token);
        }
        /// <summary>
        /// 异步根据刷新token获取当前token
        /// </summary>
        /// <param name="refreshToken">刷新token</param>
        /// <returns></returns>
        public async Task<Token> GetTokenByRefreshTokenAsync(string refreshToken)
        {
            return await repository.GetTokenByRefreshTokenAsync(refreshToken);
        }
        /// <summary>
        /// 异步获取token
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="accessExpire"></param>
        /// <param name="refreshExpire"></param>
        /// <param name="claims"></param>
        /// <param name="creatorUserId"></param>
        /// <param name="creatorTime"></param>
        /// <returns></returns>
        public async Task<AccessTokenOut?> GetTokenPairAsync(string appId, string appSecret, int accessExpire, int refreshExpire, Dictionary<string, string> claims, long creatorUserId, DateTime creatorTime)
        {
            var tokenModel = await GetTokenByAppIdAsync(appId);
            if (tokenModel == null)
            {
                string accessToken = TokenHelper.GenerateToken(appId, appSecret, accessExpire, claims);
                string refreshToken = TokenHelper.GenerateToken(appId, appSecret, refreshExpire, new Dictionary<string, string>());
                Token token = new Token()
                {
                    Id = IdGeneratorHelper.IdSnowflake(),
                    AccessToken = accessToken,
                    AccessExpires = accessExpire,
                    AccessExpiresDate = DateTime.UtcNow.AddMinutes(accessExpire),
                    RefreshToken = refreshToken,
                    RefreshExpires = refreshExpire,
                    RefreshExpiresDate = DateTime.UtcNow.AddMinutes(refreshExpire),
                    CreatorTime = creatorTime,
                    CreatorUserId = creatorUserId,
                    LastModifyTime = creatorTime,
                    LastModifyUserId = creatorUserId
                };
                int num = await InsertAsync(token);
                if (num > 0)
                {
                    return new AccessTokenOut { access_token = accessToken, access_expire = accessExpire, refresh_token = refreshToken, refresh_expire = refreshExpire };
                }
            }
            else
            {
                return new AccessTokenOut { access_token = tokenModel.AccessToken, access_expire = tokenModel.AccessExpires, refresh_token = tokenModel.RefreshToken, refresh_expire = tokenModel.RefreshExpires };
            }
            return default;
        }
        /// <summary>
        /// 异步验证token
        /// </summary>
        /// <param name="token"></param>
        /// <returns>true 有效的(刷新)令牌；false 无效的(刷新)令牌</returns>
        public async Task<bool> ValidateTokenAsync(string token)
        {
            Tuple<string, string, Dictionary<string, string>> result = TokenHelper.GetParseClaims(token, true);
            string payload = result.Item1;
            string oldSignature = result.Item2;
            Dictionary<string, string> claims = result.Item3;
            if (!string.IsNullOrEmpty(payload))
            {
                if (!string.IsNullOrEmpty(oldSignature))
                {
                    if (claims.Count > 0)
                    {
                        string appId = claims["appid"];
                        var expiryTicks = long.Parse(claims["exp"]);
                        if (DateTime.UtcNow.Ticks < expiryTicks)
                        {
                            //判断是否已经吊销
                            var model = await GetTokenIsRevokedAsync(token);
                            if (model != null)
                            {
                                var app = await appRepository.GetAppAsync(appId);
                                if (app != null)
                                {
                                    using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(app.AppSecret));
                                    string signature = Convert.ToBase64String(hmac.ComputeHash(Convert.FromBase64String(payload)));
                                    return oldSignature == signature;
                                }
                            }
                        }
                    }
                }
            }
            return false;
        }
        /// <summary>
        /// 异步通过RefreshToken获取旧Token
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <param name="accessExpire"></param>
        /// <returns></returns>
        public async Task<RefreshTokenOut?> ForceRefreshAsync(string refreshToken, int accessExpire, long lastModifyUserId, DateTime lastModifyTime)
        {
            var model = await GetTokenByRefreshTokenAsync(refreshToken);
            if (model != null)
            {
                Dictionary<string, string> claims = TokenHelper.GetParseClaims(model.AccessToken, true).Item3;
                if (claims.Count > 0)
                {
                    string appId = claims["appid"];
                    var app = await appRepository.GetAppAsync(appId);
                    if (app != null)
                    {
                        Dictionary<string, string> claimsDic = TokenHelper.GetParseClaims(model.AccessToken, false).Item3;
                        string newToken = TokenHelper.GenerateToken(appId, app.AppSecret, accessExpire, claimsDic);
                        if (!string.IsNullOrEmpty(newToken))
                        {
                            unitOfWork.BeginTran();
                            try
                            {
                                await repository.UpdateAsync(newToken, accessExpire, lastModifyUserId, lastModifyTime, model.Id);//更新令牌;
                                await tokenLogRepository.InsertAsync(new TokenLog() { NewAccessToken = newToken, OldAccessToken = model.AccessToken, OldAccessExpiresDate = model.AccessExpiresDate, CreatedDate = DateTime.Now }); //记录刷新记录
                                unitOfWork.CommitTran();

                                return new RefreshTokenOut()
                                {
                                    access_token = newToken,
                                    access_expire = accessExpire
                                };
                            }
                            //catch (Exception ex)
                            catch
                            {
                                unitOfWork.RollbackTran();
                            }
                        }
                    }
                }
            }
            return default;
        }
        #endregion 异步方法
    }
}
